package com.googlecode.afx.view.behavior.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javafx.scene.Node;

import com.googlecode.afx.utils.AFXUtils;
import com.googlecode.afx.view.NodeHandler;
import com.googlecode.afx.view.behavior.ViewBehavior;
import com.googlecode.afx.view.behavior.ViewCondition;

/**
 * Implementation of the <tt>ViewBehavior</tt> interface.
 * 
 * @author MartinKoster
 *
 */
public class ConditionBasedViewBehavior implements ViewBehavior {

	private static final Log LOG = LogFactory.getLog(ConditionBasedViewBehavior.class);
	
	private List<String> nodesToCheck = new ArrayList<String>();
	
	private List<String> nodesToHandle = new ArrayList<String>();
	
	private List<ViewCondition> viewConditions = new ArrayList<ViewCondition>();
	
	private List<NodeHandler> nodeHandler = new ArrayList<NodeHandler>();
	
	public ConditionBasedViewBehavior addCondition(ViewCondition condition) {
		this.viewConditions.add(condition);
		return this;
	}

	public ConditionBasedViewBehavior addNodesToCheck(String[] nodeList) {
		if(nodeList == null) {
			return this;
		}
		for(String id : nodeList) {
			this.nodesToCheck.add(id);
		}
		return this;
	}

	public ConditionBasedViewBehavior addNodeToCheck(String node) {
		this.nodesToCheck.add(node);
		return this;
	}

	public ConditionBasedViewBehavior addNodeHandler(NodeHandler handler) {
		this.nodeHandler.add(handler);
		return this;
	}

	public ConditionBasedViewBehavior addNodesToHandle(String[] nodeList) {
		if(nodeList == null) {
			return this;
		}
		for(String id : nodeList) {
			this.nodesToHandle.add(id);
		}
		return this;
	}

	public ConditionBasedViewBehavior addNodeToHandle(String node) {
		this.nodesToHandle.add(node);
		return this;
	}

	@Override
	public void evaluateAndApply(Node root) {
		if(this.areConditionsMet(root)) {
			List<Node> nodeList = this.retrieveNodeList(root);
			this.applyNodeHandler(root, nodeList);
		}
	}

	/**
	 * Checks, if all added conditions are met.
	 * 
	 * @param root
	 * @return
	 */
	protected boolean areConditionsMet(Node root) {
		boolean result = true;
		if(this.viewConditions != null && this.viewConditions.size() > 0) {
			for(ViewCondition condition : viewConditions) {
				String[] nodes = new String[nodesToCheck.size()];
				nodes = nodesToCheck.toArray(nodes);
				result &= condition.meets(root, nodes);
				LOG.debug("Condition '" + condition + "' resulted to '" + result + "'");
			}
		}
		
		return result;
	}
	
	/**
	 * Applies all added <tt>NodeHandler</tt>s.
	 * @param root
	 */
	protected void applyNodeHandler(Node root, List<Node> nodeList) {
		if(this.nodeHandler != null && this.nodeHandler.size() > 0) {
			for(NodeHandler handler : this.nodeHandler) {
				for(Node node : nodeList) {
					handler.process(node);
				}
			}
		}
	}
	
	/**
	 * Extracts all relevant nodes to handle from the given scene graph 
	 * represented by <tt>root</tt>.
	 * 
	 * @param root
	 * @return
	 */
	protected List<Node> retrieveNodeList(Node root) {
		List<Node> nodeList = new ArrayList<Node>();
		if(this.nodesToHandle != null && this.nodesToHandle.size() > 0) {
			for(String id : this.nodesToHandle) {
				Node node = AFXUtils.findNodeById(root, id);
				if(node == null) {
					LOG.warn("Node with id='" + id + "' can not be retrieved from the scene graph. Is there a configuration error?");
				}
				nodeList.add(node);
			}
		}
		return nodeList;
	}
}
